 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.update.internal.core;

 import java.io.*;
 import java.net.URL ;
 import java.util.*;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.internal.provisional.verifier.CertificateVerifierFactory;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.update.core.*;
 import org.eclipse.update.internal.jarprocessor.JarProcessor;
 import org.eclipse.update.internal.jarprocessor.Utils;
 import org.eclipse.update.internal.security.JarVerifier;
 import org.eclipse.update.internal.verifier.CertVerifier;

 /**
  * Content Provider of a Feature Package
  */
 public class FeaturePackagedContentProvider extends FeatureContentProvider {

     private ContentReference localManifest = null;
     private ContentReference[] localFeatureFiles = new ContentReference[0];
     private IVerifier jarVerifier = null;
     private ExtendedSite siteModel = null;
     private boolean continueOnError;
     /*
      * filter for file with .jar
      */
     public static final FilenameFilter filter = new FilenameFilter() {
         public boolean accept(File dir, String name) {
             return name.endsWith(FeaturePackagedContentProvider.JAR_EXTENSION);
         }
     };

     /*
      * Constructor
      */
     public FeaturePackagedContentProvider(URL url, ISite site) {
         super(url);
         if (site instanceof ExtendedSite) {
             this.siteModel = (ExtendedSite) site;
         }
     }
     
     /*
      * Returns a new verifier for each top-level install
      * (if the verifier has a parent, return the verifier
      * otherwise reinitialize)
      */
     public IVerifier getVerifier() throws CoreException {
         CertificateVerifierFactory factory = UpdateCore.getPlugin().getVerifierFactory();
         if (jarVerifier == null || jarVerifier.getParent() == null) {
             if (factory != null)
                 jarVerifier = new CertVerifier(UpdateCore.getPlugin().getVerifierFactory());
             else
                 jarVerifier = new JarVerifier();
             return jarVerifier;
         }

         // re-init will be done if the parent changes
 return jarVerifier;
     }

     /*
      * @see IFeatureContentProvider#getFeatureManifestReference()
      */
     public ContentReference getFeatureManifestReference(InstallMonitor monitor) throws CoreException {

         // check to see if we already have local copy of the manifest
 if (localManifest != null)
             return localManifest;
         ContentReference[] featureArchiveReference = getFeatureEntryArchiveReferences(monitor);
         JarContentReference featureJarReference = null;
         try {

             // force feature archive to local.
 // This content provider always assumes exactly 1 archive file (index [0])
 featureJarReference = (JarContentReference) asLocalReference(featureArchiveReference[0], null);
             // we need to unpack archive locally for UI browser references to be resolved correctly
 localFeatureFiles = featureJarReference.unpack(getWorkingDirectory(), null, monitor);
         } catch (IOException e) {
             throw errorRetrieving(Feature.FEATURE_XML, featureJarReference, e);
         }

         // find the manifest in the unpacked feature files
 for (int i = 0; i < localFeatureFiles.length; i++) {
             if (localFeatureFiles[i].getIdentifier().equals(Feature.FEATURE_XML)) {
                 localManifest = localFeatureFiles[i];
                 // cache reference to manifest
 return localManifest;
             }
         }

         // the manifest has not been found
 String [] values = new String [] { Feature.FEATURE_XML, getURL().toExternalForm()};
         throw Utilities.newCoreException(NLS.bind(Messages.FeaturePackagedContentProvider_NoManifestFile, values), new Exception ());

     }

     /*
      * @see IFeatureContentProvider#getArchiveReferences()
      */
     public ContentReference[] getArchiveReferences(InstallMonitor monitor) throws CoreException {

         IPluginEntry[] entries = getFeature().getPluginEntries();
         INonPluginEntry[] nonEntries = getFeature().getNonPluginEntries();
         List listAllContentRef = new ArrayList();
         ContentReference[] allContentRef = new ContentReference[0];

         // feature
 listAllContentRef.addAll(Arrays.asList(getFeatureEntryArchiveReferences(monitor)));

         // plugins
 for (int i = 0; i < entries.length; i++) {
             listAllContentRef.addAll(Arrays.asList(getPluginEntryArchiveReferences(entries[i], monitor)));
         }

         // non plugins
 for (int i = 0; i < nonEntries.length; i++) {
             listAllContentRef.addAll(Arrays.asList(getNonPluginEntryArchiveReferences(nonEntries[i], monitor)));
         }

         // transform List in Array
 if (listAllContentRef.size() > 0) {
             allContentRef = new ContentReference[listAllContentRef.size()];
             listAllContentRef.toArray(allContentRef);
         }

         return allContentRef;
     }

     /*
      * @see IFeatureContentProvider#getFeatureEntryArchiveReferences()
      */
     public ContentReference[] getFeatureEntryArchiveReferences(InstallMonitor monitor) throws CoreException {

         //1 jar file <-> 1 feature
 // we will return the JAR file
 ContentReference[] references = new ContentReference[1];
         ContentReference currentReference = null;
         String archiveID = null;

         try {
             archiveID = (getFeature() != null) ? getFeature().getVersionedIdentifier().toString() : ""; //$NON-NLS-1$
 currentReference = new JarContentReference(archiveID, getURL());
             currentReference = asLocalReference(currentReference, monitor);
             references[0] = currentReference;
         } catch (IOException e) {
             references[0] = continueOnErrorOrRethrow(archiveID, e);
         }
         return references;
     }

     /*
      * @see IFeatureContentProvider#getPluginEntryArchiveReferences(IPluginEntry)
      */
     public ContentReference[] getPluginEntryArchiveReferences(IPluginEntry pluginEntry, InstallMonitor monitor) throws CoreException {

         // 1 plugin <-> 1 jar
 // we return the JAR file
 ContentReference[] references = new ContentReference[1];
         String archiveID = getPathID(pluginEntry);
         ISite site = (getFeature() == null) ? null : getFeature().getSite();
         ISiteContentProvider siteContentProvider = (site == null) ? null : site.getSiteContentProvider();
         URL url = (siteContentProvider == null) ? null : siteContentProvider.getArchiveReference(archiveID);

         try {
             references[0] = retrieveLocalJar(new JarContentReference(archiveID, url), monitor);
         } catch (IOException e) {
             references[0] = continueOnErrorOrRethrow(archiveID, e);
         }
         return references;
     }

     private ContentReference retrieveLocalJar(JarContentReference reference, InstallMonitor monitor) throws IOException, CoreException {
         //If the site does not support pack200, just get the jar as normal
 if(siteModel == null || !siteModel.supportsPack200() || !JarProcessor.canPerformUnpack()) {
             ContentReference contentReference = null;
             try {
                 contentReference = asLocalReference(reference, monitor);
             }
             catch (FileNotFoundException e) {
                 contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
             }
             catch (IOException e) {
                 contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
             }
             catch (CoreException e) {
                 contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
             }
             return contentReference;
         }
         
         ContentReference packedRef = null;
         String key = reference.toString();
         Object jarLock = LockManager.getLock(key);
         synchronized (jarLock) {
             //do we have this jar already?
 File localFile = Utilities.lookupLocalFile(key);
             if (localFile != null) {
                 // check if the cached file is still valid (no newer version on server)
 if (UpdateManagerUtils.isSameTimestamp(reference.asURL(), localFile.lastModified())) {
                     LockManager.returnLock(key);
                     return reference.createContentReference(reference.getIdentifier(), localFile);
                 }
             }

             try {
                 //don't have jar, check for pack.gz
 URL packGZURL = new URL (reference.asURL().toExternalForm() + ".pack.gz"); //$NON-NLS-1$
 packedRef = asLocalReference(new JarContentReference(reference.getIdentifier(), packGZURL), monitor);
             } catch (IOException e) {
                 //no pack.gz
 } catch (CoreException e){
                 //no pack.gz
 }
         }
         
         if (packedRef == null) {
             //no pack.gz on server, get normal jar
 ContentReference contentReference = null;
             try {
                 contentReference = asLocalReference(reference, monitor);
             }
             catch (FileNotFoundException e) {
                 contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
             }
             catch (IOException e) {
                 contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
             }
             catch (CoreException e) {
                 contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
             }
             return contentReference;
         }

         boolean success = false;
         synchronized (jarLock) {
             String packed = packedRef.toString();
             Object packedLock = LockManager.getLock(packed);
             synchronized (packedLock) {
                 try {
                     File tempFile = packedRef.asFile();
                     long timeStamp = tempFile.lastModified();
     
                     JarProcessor processor = JarProcessor.getUnpackProcessor(null);
                     processor.setWorkingDirectory(tempFile.getParent());
     
                     File packedFile = new File(tempFile.toString() + Utils.PACKED_SUFFIX);
                     tempFile.renameTo(packedFile);
                     
                     if (monitor != null) {
                         monitor.saveState();
                         monitor.subTask(Messages.JarContentReference_Unpacking + " " + reference.getIdentifier() + Utils.PACKED_SUFFIX); //$NON-NLS-1$
 monitor.showCopyDetails(false);
                     }
                     //unpacking the jar will strip the ".pack.gz" and leave us back with the original filename
 try {
                         processor.processJar(packedFile);
                     } catch (Throwable e) {
                         //something is wrong unpacking
 }
     
                     if(tempFile.exists() && tempFile.length() > 0){
                         success = true;
                         tempFile.setLastModified(timeStamp);
                         Utilities.mapLocalFile(key, tempFile);
                         UpdateCore.getPlugin().getUpdateSession().markVisited(reference.asURL());
                     }
                 } finally {
                     LockManager.returnLock(packed);
                     LockManager.returnLock(key);
                     if(monitor != null)
                         monitor.restoreState();
                 }
             }
         }
         if(!success){
             //Something went wrong with the unpack, get the normal jar.
 ContentReference contentReference = null;
             try {
                 contentReference = asLocalReference(reference, monitor);
             }
             catch (FileNotFoundException e) {
                 contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
             }
             catch (IOException e) {
                 contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
             }
             catch (CoreException e) {
                 contentReference = continueOnErrorOrRethrow(reference.getIdentifier(), e);
             }
             return contentReference;
         }
         return packedRef;
     }

     /*
      * @see IFeatureContentProvider#getNonPluginEntryArchiveReferences(INonPluginEntry)
      */
     public ContentReference[] getNonPluginEntryArchiveReferences(INonPluginEntry nonPluginEntry, InstallMonitor monitor) throws CoreException {

         // archive = feature/<id>_<ver>/<file>
 String archiveID = Site.DEFAULT_FEATURE_PATH + ((getFeature() != null) ? getFeature().getVersionedIdentifier().toString() : ""); //$NON-NLS-1$
 archiveID += "/" + nonPluginEntry.getIdentifier(); //$NON-NLS-1$

         ContentReference[] references = new ContentReference[1];
         ContentReference currentReference = null;

         try {
             ISite site = (getFeature() == null) ? null : getFeature().getSite();
             ISiteContentProvider siteContentProvider = (site == null) ? null : site.getSiteContentProvider();
             URL url = (siteContentProvider == null) ? null : siteContentProvider.getArchiveReference(archiveID);

             currentReference = new ContentReference(nonPluginEntry.getIdentifier(), url);
             currentReference = asLocalReference(currentReference, monitor);
             references[0] = currentReference;

         } catch (IOException e) {
             references[0] = continueOnErrorOrRethrow(archiveID, e);
         }

         return references;
     }


     /*
      * @see IFeatureContentProvider#getFeatureEntryContentReferences()
      */
     public ContentReference[] getFeatureEntryContentReferences(InstallMonitor monitor) throws CoreException {

         return localFeatureFiles; // return cached feature references
 // Note: assumes this content provider is always called first to
 // get the feature manifest. This forces the feature files
 // to be unpacked and caches the references
 }

     /*
      * @see IFeatureContentProvider#getPluginEntryContentReferences(IPluginEntry)
      */
     public ContentReference[] getPluginEntryContentReferences(IPluginEntry pluginEntry, InstallMonitor monitor) throws CoreException {

         ContentReference[] references = getPluginEntryArchiveReferences(pluginEntry, monitor);
         ContentReference[] pluginReferences = new ContentReference[0];

         try {
             if (references[0] instanceof JarContentReference) {
                 JarContentReference localRef = (JarContentReference) asLocalReference(references[0], monitor);
                 pluginReferences = localRef.peek(null, monitor);
             } else {
                 // return the list of all subdirectories
 List files = getFiles(references[0].asFile());
                 pluginReferences = new ContentReference[files.size()];
                 for (int i = 0; i < pluginReferences.length; i++) {
                     File currentFile = (File) files.get(i);
                     pluginReferences[i] = new ContentReference(null, currentFile.toURL());
                 }
             }

             //[20866] we did not preserve executable bit
 validatePermissions(pluginReferences);

         } catch (IOException e) {
             throw errorRetrieving(pluginEntry.getVersionedIdentifier().toString(), references[0], e);
         }
         return pluginReferences;
     }

     /*
      * return all the files under the directory
      */
     private List getFiles(File dir) throws IOException {
         List result = new ArrayList();

         if (!dir.isDirectory())
             throw new IOException(NLS.bind(Messages.FeaturePackagedContentProvider_InvalidDirectory, (new String [] { dir.getPath() })));

         File[] files = dir.listFiles();
         if (files != null) // be careful since it can be null
 for (int i = 0; i < files.length; ++i) {
                 if (files[i].isDirectory()) {
                     result.addAll(getFiles(files[i]));
                 } else {
                     result.add(files[i]);
                 }
             }
         return result;
     }

     /*
      *
      */
     private CoreException errorRetrieving(String obj, ContentReference archive, Exception e) {

         String [] values = new String [] { obj };

         return Utilities.newCoreException(NLS.bind(Messages.FeaturePackagedContentProvider_ErrorRetrieving, values), e);

     }


     public void setContinueOnError(boolean continueOnError) {
         this.continueOnError = continueOnError;
     }
     
     /**
      * This method is used for when a core exception is detected, so, if its decided to rethrow, then
      * a core exception odes not have to be recreated.
      *
      * @param archiveID id of the archive file
      * @param CoreException
      * @return NullReference if its decided not to continue
      * @throws CoreException
      */
     /*private ContentReference continueOrErrorOrRethrow(String archiveID, CoreException coreException) throws CoreException {
         ContentReference reference = null;

         if (continueOnError) {
             // this ContentReference without a file or URL is purely a
             // "missing jar" reference.
             reference = new NullContentReference(archiveID);

             String msg = " ContinueOnError: The following ID was not found, so was skipped, and is not on miror site: " + archiveID; //$NON-NLS-1$
             String id = UpdateCore.getPlugin().getBundle().getSymbolicName();
             IStatus status = new Status(IStatus.WARNING, id , 0, msg, null);
             UpdateCore.log(status);
             
         }
         else {
             throw coreException;
         }
         return reference;
     }*/
     
     private ContentReference continueOnErrorOrRethrow(String archiveID, Exception e) throws CoreException {
         ContentReference reference = null;

         if (continueOnError) {
             // this ContentReference without a file or URL is purely a
 // "missing jar" reference.
 reference = new NullContentReference(archiveID);
             
             String msg = " ContinueOnError: The following ID was not found, so was skipped, and is not on miror site: " + archiveID; //$NON-NLS-1$
 String id = UpdateCore.getPlugin().getBundle().getSymbolicName();
             IStatus status = new Status(IStatus.WARNING, id , 0, msg, null);
             UpdateCore.log(status);
             
         }
         else {
             throw errorRetrieving(archiveID, reference, e);
         }
         return reference;
     }

 }

